package demo.java.io;

import demo.java.time.DateTimeDemo;
import org.junit.Test;
import org.springframework.util.ResourceUtils;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.DirectoryStream;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * <h2>getPath()、getAbsolutePath()、getCanonicalPath()的区别</h2>
 * <li>getPath()获取的是新建文件时的路径
 * <li>getAbsolutePath()获取的是文件的绝对路径，返回当前目录的路径+构造file时候的路径,
 * <li>getCanonicalPath()获取的也是文件的绝对路径，而且把..或者.这样的符号解析出来，
 */
public class FileDemo {

    public static void main(String[] args) {
        System.out.println(File.separator);
    }

    /**
     * 查找匹配的文件
     *
     * @param baseDirectory
     * @param filePattern
     * @return
     * @throws IOException
     */
    public static List<File> walkAndMatchFiles(String baseDirectory, String filePattern) throws IOException {
        PathMatcher pathMatcher = FileSystems.getDefault().getPathMatcher(filePattern);
        Path basePath = Paths.get(baseDirectory);
        try (Stream<Path> stream = Files.walk(basePath)) {
            return stream.filter(p -> {
                        boolean isRegularFile = Files.isRegularFile(p);
                        boolean fileMatch = pathMatcher.matches(p);
                        System.out.println(p + " ,isRegularFile = " + isRegularFile + " ,match(" + filePattern + ") = " + fileMatch);
                        return isRegularFile && fileMatch;
                    }).map(Path::toFile)
                    .collect(Collectors.toList());
        }
    }

    /**
     * 查找匹配的文件。支持路径通配符和文件通配符
     *
     * @param filePattern
     * @return
     * @throws IOException
     */
    public static List<File> matchFiles(String filePattern) throws IOException {
        if (filePattern == null || filePattern.isEmpty()) {
            throw new IllegalArgumentException("filePattern is empty");
        }
        File f = new File(filePattern);
        File parentDir = f.getParentFile();
        if (parentDir == null) {
            throw new IllegalArgumentException("invalid filePattern: " + filePattern);
        }
        Path parentPath = parentDir.toPath();
        String fileRegex = f.getName();
        PathMatcher fileMatcher = FileSystems.getDefault().getPathMatcher("regex:" + fileRegex);
        // 判断父目录是否存在，如果存在则直接进行文件匹配;不存在，则判断是否走了统配符匹配的多目录
        if (parentDir.exists()) {
            return listFile(parentDir, fileMatcher);
        }
        List<Path> matchedParentPath = matchDir(parentPath);
        return matchedParentPath.stream()
                .map(p -> {
                    try {
                        return listFile(p.toFile(), fileMatcher);
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                }).flatMap(List::stream)
                .collect(Collectors.toList());
    }

    /**
     * 匹配文件目录。如果该目录存在，则直接返回该目录；
     * 如果不存在，判断是否含有正则表达式（如：/var/log/{.*}/，其中大括号{}中的目录是需要正则配的）
     *
     * @param parentPath
     * @return
     */
    private static List<Path> matchDir(Path parentPath) {
        if (parentPath == null) {
            return Collections.emptyList();
        }
        // 如果该目录存在，则直接返回该目录；
        if (parentPath.toFile().exists()) {
            return Arrays.asList(parentPath);
        }

        Path rootPath = parentPath.getRoot();
        // 没有根路径，不是绝对路径，不支持,直接抛异常
        if (rootPath == null) {
            throw new IllegalArgumentException("invalid filePattern: " + parentPath);
        }

        int nameCount = parentPath.getNameCount();
        // 如 ： / 或 /error.log
        if (nameCount <= 1) {
            return Collections.emptyList();
        }
        List<Path> matchedParentPath = Arrays.asList(rootPath);
        for (int i = 0; i < nameCount; i++) {
            Path idxPath = parentPath.getName(i);
            String pathName = idxPath.toString();
            // 设定的规则是 {} 中间的数据为正则路径，需要解析匹配
            if (pathName.startsWith("{") && pathName.endsWith("}")) {
                String dirRegex = pathName.substring(1, pathName.length() - 1);
                matchedParentPath = matchedParentPath.stream()
                        .map(p -> {
                                    try {
                                        return listDirectory(p, dirRegex);
                                    } catch (IOException e) {
                                        throw new RuntimeException(e);
                                    }
                                }
                        ).flatMap(List::stream).collect(Collectors.toList());
            } else {
                matchedParentPath = matchedParentPath.stream()
                        .map(p -> p.resolve(idxPath))
                        .collect(Collectors.toList());
            }
        }
        return matchedParentPath;
    }

    private static List<Path> listDirectory(Path parentDir, String dirRegex) throws IOException {
        // 判断父目录是否存在
        if (!parentDir.toFile().exists()) {
            return Collections.emptyList();
        }
        PathMatcher fileMatcher = FileSystems.getDefault().getPathMatcher("regex:" + dirRegex);
        DirectoryStream.Filter<Path> dirFilter = new DirectoryStream.Filter<Path>() {
            @Override
            public boolean accept(Path path) throws IOException {
                return fileMatcher.matches(path.getFileName()) && Files.isDirectory(path);
            }
        };
        List<Path> result = new ArrayList<>();
        try (DirectoryStream<Path> stream = Files.newDirectoryStream(parentDir, dirFilter)) {
            for (Path path : stream) {
                result.add(path);
            }
        }
        return result;
    }

    /**
     * 列出父目录下所有匹配的文件
     *
     * @param parentDir
     * @param fileMatcher
     * @return
     * @throws IOException
     */
    private static List<File> listFile(File parentDir, PathMatcher fileMatcher) throws IOException {
        // 判断父目录是否存在
        if (!parentDir.exists()) {
            return Collections.emptyList();
        }
        DirectoryStream.Filter<Path> fileFilter = new DirectoryStream.Filter<Path>() {
            @Override
            public boolean accept(Path path) throws IOException {
                return fileMatcher.matches(path.getFileName()) && !Files.isDirectory(path);
            }
        };
        List<File> result = new ArrayList<>();
        try (DirectoryStream<Path> stream = Files.newDirectoryStream(parentDir.toPath(), fileFilter)) {
            for (Path entry : stream) {
                result.add(entry.toFile());
            }
        }
        return result;
    }

    /**
     * 查找匹配的文件
     *
     * @param baseDirectory
     * @param glob
     * @return
     * @throws IOException
     */
    public static List<File> matchFiles2(String baseDirectory, String glob) throws IOException {
        Path basePath = Paths.get(baseDirectory);

        List<File> list = new ArrayList<>();
        try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(basePath, glob)) {
            for (Path path : directoryStream) {
                list.add(path.toFile());
            }
        }
        return list;
    }

    public static List<String> listClassNamesInPackage(String packageName) throws IOException, URISyntaxException {
        List<String> classes = new ArrayList<>();
        Enumeration<URL> resources = Thread.currentThread().getContextClassLoader()
                .getResources(packageName.replace('.', File.separatorChar));
        if (!resources.hasMoreElements()) {
            throw new IllegalStateException("No package found: " + packageName);
        }
        PathMatcher pathMatcher = FileSystems.getDefault().getPathMatcher("glob:*.class");
        while (resources.hasMoreElements()) {
            URL resource = resources.nextElement();
            Files.walkFileTree(Paths.get(resource.toURI()), new SimpleFileVisitor<Path>() {
                @Override
                public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) throws IOException {
                    if (pathMatcher.matches(path.getFileName())) {
                        try {
                            String className = Paths.get(resource.toURI()).relativize(path).toString().replace(File.separatorChar, '.');
                            classes.add(packageName + '.' + className.substring(0, className.length() - 6));
                        } catch (URISyntaxException e) {
                            throw new IllegalStateException(e);
                        }
                    }
                    return FileVisitResult.CONTINUE;
                }
            });
        }
        return classes;
    }

    public static void printPath(Path path) {
        System.out.println("----------" + path);
        System.out.println("FileName = " + path.getFileName());
        System.out.println("Parent = " + path.getParent());
        System.out.println("Root = " + path.getRoot());
        System.out.println("NameCount = " + path.getNameCount());
        if (path.getNameCount() > 0) {
            System.out.println("Name[0] = " + path.getName(0));
            System.out.println("subPath(0,2) = " + path.subpath(0, 1));
        }
        System.out.println("Absolute = " + path.isAbsolute());

        System.out.println("URI = " + path.toUri());
        System.out.println("AbsolutePath = " + path.toAbsolutePath());
    }

    @Test
    public void testPathMatcher() throws IOException, URISyntaxException {
        PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:**/*.xml");
        System.out.println(matcher.matches(Paths.get("D:/tmp/log4j2-test.xml")));

        List<File> list = walkAndMatchFiles("/var/log/", "glob:**/*.log");
        list.forEach(System.err::println);
        System.out.println("------------");
        list = matchFiles2("/var/log/", "*.out");
        list.forEach(System.err::println);
        System.out.println("------------");
        List<String> classNames = listClassNamesInPackage("demo.java.io");
        System.out.println(classNames);
    }

    @Test
    public void testMatchFiles() throws IOException {
        List<File> list = matchFiles("/var/log/{.+}/.+auth");
        list.forEach(f -> System.out.println(f.getPath()));
    }

    /**
     * 列出父目录下所有匹配的文件
     *
     * @param parentDir
     * @param fileRegex
     * @return
     * @throws IOException
     */
    public static List<File> listFile(File parentDir, String fileRegex) throws IOException {
        // 判断父目录是否存在
        if (!parentDir.exists()) {
            return Collections.emptyList();
        }
        PathMatcher fileMatcher = FileSystems.getDefault().getPathMatcher("regex:" + fileRegex);
        return listFile(parentDir, fileMatcher);
    }

    /**
     * Path表示的是一个目录名序列，其后还可以跟着一个文件名。
     * 注：文件系统的分隔符（类Unix文件系统是 / ，Windows是 \ ）
     * 路径中的第一个部件可以是根部件，例如 / 或 C:\ 。以根部件开始的是绝对路径；否则就是相对路径。
     */
    @Test
    public void testPath() {

        Path normalPath = Paths.get("/Users/xmly/Documents/code/Demo/testJava/././src/main/java/demo/java/io/FileDemo.java");
        System.out.println(normalPath.normalize());

        Path absolute = Paths.get("/Users/xmly/Documents/code/Demo/testJava/src/main/java/demo/java/io/FileDemo.java");
        printPath(absolute);

        Path relative = Paths.get("src/main/java/demo/java/io/FileDemo.java");
        printPath(relative);

        Path dir = Paths.get(URI.create("file:///Users/xmly/Documents/code/Demo/testJava/src/main/java/demo/java/io"));
        printPath(dir);

        Path resolvedPath = dir.resolve(Paths.get("FileDemo.java"));
        printPath(resolvedPath);

        Path test = Paths.get("/var/log/pika/{*}/ERROR.log");
        printPath(test);

        test = Paths.get("/ERROR.log");
        printPath(test);

        test = Paths.get("/");
        printPath(test);

    }

    @Test
    public void testFile() throws IOException {
        String pathname = "/etc/service/springboot/arthas-output/20220225-160149.html";
        File file = new File(pathname);
        String absolutePath = file.getAbsolutePath();
        String canonicalPath = file.getCanonicalPath();
        String name = file.getName();
        String parent = file.getParent();
        String path = file.getPath();
        System.out.println("pathname = " + pathname);
        System.out.println("absolutePath = " + absolutePath);
        System.out.println("canonicalPath = " + canonicalPath);
        System.out.println("name = " + name);
        System.out.println("parent = " + parent);
        System.out.println("Path = " + path);
        System.out.println("LastModified = " + file.lastModified() + " = " + DateTimeDemo.toLocalDateTime(file.lastModified()));

    }

    /**
     * <h2>获取资源的路径</h2>
     *
     * <li>1:输入path 以'/' 或者 ’\\‘ 开头的 ，是以项目所在的硬盘位基础路径
     * <li>2:输入path 以 字母开头 的,是以项目的路径为基础路径 即： System.getProperty("user.dir")
     * <li>3.输入绝对路径的，就是以该绝对路径做为路径咯
     * <li>获取上级目录:getParent()或者getParentFile();
     */
    @Test
    public void demoPath() {
        File file = new File("/");
        System.out.println("根目录：" + file.getAbsolutePath());

        file = new File("");
        System.out.println("获取当前类的所在工程路径：" + file.getAbsolutePath());

        System.out.println("获取当前工程路径 :" + System.getProperty("user.dir"));

        System.out.println(System.getProperty("java.class.path"));
    }

    /**
     * 获取资源路径
     */
    @Test
    public void testGetResource() {
        URL classPathRoot = FileDemo.class.getResource("/");
        System.out.println("classpath目录：getResource(\"/\") = " + classPathRoot);
        System.out.println(classPathRoot.getPath());

        URL classPath = FileDemo.class.getResource("");
        System.out.println("classpath + 该类所在路径：getResource(\"\") = " + classPath);

        URL classPathRoot2 = FileDemo.class.getClassLoader().getResource("");
        System.out.println("classpath目录：getClassLoader().getResource(\"\") = " + classPathRoot2);

        // null
        URL resource3 = FileDemo.class.getClassLoader().getResource("/");
        System.out.println("getClassLoader().getResource(\"/\") = " + resource3);
    }

    public void test2() {
        // classpath下code.txt
        InputStream asStream = FileDemo.class.getClassLoader().getResourceAsStream("code.txt");
        System.out.println(asStream);
        // 加 / 表示获取classpath下code.txt,不加/返回为null
        InputStream asStream1 = FileDemo.class.getResourceAsStream("/code.txt");
        System.out.println(asStream1);
        // 获取classpath下code.txt文件
        InputStream asStream2 = FileDemo.class.getClassLoader().getSystemResourceAsStream("code.txt");
        System.out.println(asStream2);
        System.out.println("--------------------------------------");
        // 使用工具类ResourceUtils获取classpath下文件
        File file1;
        try (InputStream inputStream = FileDemo.class.getResource("/code.txt").openStream();
             FileOutputStream fos = new FileOutputStream("C:\\Users\\18030501\\Desktop\\aaa.txt");) {
            file1 = ResourceUtils.getFile("classpath:code.txt");
            FileInputStream is = new FileInputStream(file1);
            System.out.println(is);
            System.out.println("---------使用URL对象打开文件流,例子如下---------");


            byte[] bys = new byte[1024];
            int len;
            while ((len = inputStream.read(bys)) != -1) {
                fos.write(bys, 0, len);
                fos.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
    @Test
    public void demoDelete() throws IOException {
        File file = new File("/Users/xmly/.m2/repository/com/ximalaya/danube/danube-core/1.1.11/");
        // 布尔方法仅返回 false，而没有提供失败原因的任何迹象
        boolean deleted = file.delete();
        System.err.println("删除文件返回："+deleted + ", 文件是否还在："+file.exists());
        // delete 方法失败时，这个无返回值的方法会抛出一系列异常类型之一，以更好地指示失败的原因
        Files.delete(file.toPath());
    }

    /**
     * deleteOnExit() 方法是一个非常有用的功能。它的主要作用是注册一个钩子，在 Java 虚拟机（JVM）正常终止时删除指定的文件。
     * 换句话说，这个方法确保了即使程序异常退出，文件也会在 JVM 关闭时被删除。
     *
     * 当您调用 deleteOnExit() 方法时，该文件将被标记为在 JVM 退出时删除。您可以在程序的任何地方使用此方法。
     * 例如，您可能想要在创建临时文件时使用它，以确保这些临时文件在程序结束时自动清理。
     *
     * 注意事项
     * <li>仅适用于普通文件：deleteOnExit() 方法只能用于普通文件，而不能用于目录。
     * <li>调用时机：建议最好在文件创建后立即调用该方法，以确保它在 JVM 退出时能够正常删除文件。
     * <li>执行顺序：如果您注册了多个文件以在 JVM 退出时删除，则它们将在 JVM 退出时按注册的顺序删除。
     * <li>内存问题：由于 JVM 会在其关闭过程中处理这些注册的删除任务，因此在某些情况下，过多的注册可能会导致内存使用过高。
     * <li>异常处理：如果文件在程序运行时被删除或移动，JVM 在关闭时可能会抛出异常，但该异常会被忽略。
     */
    @Test
    public void demoDeleteOnExit(){
        String filePath = "/Users/xmly/.m2/repository/com/ximalaya/danube/danube-core/1.1.11/danube-core-1.1.11.jar";
        File file = new File(filePath);
        file.deleteOnExit();
        System.err.println("deleteOnExit后, 文件是否还在："+file.exists());
    }

}
